// Copyright (C) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.IO;

namespace Microsoft.VSPowerToys.VSCmdShell
{
    /// <summary>
    /// This static class contains methods to load IShellHost implementations dynamically using reflection
    /// </summary>
    public static class ShellLoader
    {
        /// <summary>
        /// Tries loading the assembly and creating an instance of provided IShellHost implementation.
        /// </summary>
        /// <param name="typeName">Full typename of IShellHost implementation</param>
        /// <param name="libraryLocation">Location of library containing the shell, if it is empty no assembly will be loaded</param>
        /// <returns></returns>
        public static IShellHost LoadShell(string typeName, string libraryLocation)
        {
            IShellHost loadedShell = null;
            try
            {
                Type type = null;
                if (!String.IsNullOrEmpty(libraryLocation))
                {
                    // If there is no path information, append current assembly path
                    if (!libraryLocation.Contains(Path.DirectorySeparatorChar.ToString()))
                    {
                        string loc = Assembly.GetExecutingAssembly().Location;
                        string root = loc.Substring(0,loc.LastIndexOf(Path.DirectorySeparatorChar));
                        libraryLocation = Path.Combine(root, libraryLocation);
                    }
                    Assembly assembly = Assembly.LoadFrom(libraryLocation);
                    type = assembly.GetType(typeName, false, true);
                }
                else
                {
                    type = Type.GetType(typeName, false, true);
                }
                if (type != null)
                {
                    ConstructorInfo constructor = type.GetConstructor(new Type[] { });
                    loadedShell = (IShellHost)constructor.Invoke(new Object[] { });
                }
                else
                {
                    throw new ShellLoadException(Strings.ShellLoadError_Generic);
                }
            }
            catch (TypeLoadException)
            {
                throw new ShellLoadException(Strings.ShellLoadError_Generic);
            }
            catch (BadImageFormatException)
            {
                throw new ShellLoadException(Strings.ShellLoadError_BadImage);
            }
            catch (System.IO.IOException)
            {
                throw new ShellLoadException(Strings.ShellLoadError_FileError);
            }
            catch (ArgumentException)
            {
                throw new ShellLoadException(Strings.ShellLoadError_Generic);
            }
            return loadedShell;
        }

        /// <summary>
        /// Loads the currently selected shell by looking at settings file.
        /// </summary>
        /// <returns></returns>
        public static IShellHost LoadSelectedShell()
        {
            string selectedType = Properties.Settings.Default.SelectedShellTypeName;
            int index = Properties.Settings.Default.ShellHostsTypeNames.IndexOf(selectedType);
            if (index >= 0)
            {
                return LoadShell(selectedType, Properties.Settings.Default.ShellHostsLibraryLocation[index]);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Locates all IShellHost implementations in the provided assembly and
        /// adds them to shell selection
        /// </summary>
        /// <param name="libraryLocation">Full path to a .NET 2.0 assembly file</param>
        public static void AddShellHosts(string libraryLocation)
        {
            try
            {
                Assembly selectedAssembly = Assembly.LoadFrom(libraryLocation);
                Type[] types = selectedAssembly.GetTypes();
                foreach (Type type in types)
                {
                    if ((typeof(IShellHost)).IsAssignableFrom(type))
                    {
                        // Current type is an implementation of IShellHost interface
                        string typeName = type.ToString();
                        AddShellEntry(typeName, libraryLocation);
                    }
                }
            }
            catch (System.IO.IOException)
            {
                throw new ShellLoadException(Strings.ShellLoadError_FileError);
            }
            catch (System.BadImageFormatException)
            {
                throw new ShellLoadException(Strings.ShellLoadError_BadImage);
            }
            catch (System.Reflection.ReflectionTypeLoadException)
            {
                throw new ShellLoadException(Strings.ShellLoadError_Generic);
            }
        }

        /// <summary>
        /// Adds an entry to list in settings containing possible shell entries.
        /// </summary>
        /// <param name="typeName">Full type name of the IShellHost implementation</param>
        /// <param name="location">Full path to assembly containing IShellHost implementation</param>
        public static void AddShellEntry(string typeName, string location)
        {
            if (!Properties.Settings.Default.ShellHostsTypeNames.Contains(typeName))
            {
                Properties.Settings.Default.ShellHostsTypeNames.Add(typeName);
                Properties.Settings.Default.ShellHostsLibraryLocation.Add(location);
                Properties.Settings.Default.Save();
            }
        }
    }

    /// <summary>
    /// Exception raised when an instance of IShellHost could not be loaded.
    /// </summary>
    [Serializable]
    public class ShellLoadException : Exception
    {
         #region Constructors

        /// <summary>
        /// Default constructor (created for compatilibity)
        /// </summary>
        public ShellLoadException()
            : base()
        {
        }

        /// <summary>
        /// Constructor that should be used to create instances of this expection in the application
        /// </summary>
        /// <param name="msg">Explanation on why load failed. This message might be shown to user.</param>
        public ShellLoadException(string msg)
            : base(msg)
        {
        }

        /// <summary>
        /// Constructor created for compatibility
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="inner"></param>
        public ShellLoadException(string msg, Exception inner)
            : base(msg, inner)
        {

        }
        /// <summary>
        /// Constructor created for compatibility
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        protected ShellLoadException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        #endregion

        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }
    }
}
